package qs.openxt.libs.cv;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLDecoder;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.ExifHelper;
import org.apache.cordova.FileHelper;

import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

import qs.openxt.libs.util.AlbumUtil;

/**
 * This class launches the camera view, allows the user to take a picture,
 * closes the camera view, and returns the captured image. When the camera view
 * is closed, the screen displayed before the camera view was shown is
 * redisplayed.
 */
public class Camera implements MediaScannerConnectionClient {

	private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)

	public static final int CAMERA = 1; // Take picture from camera
	public static final int JPEG = 0; // Take a picture of type JPEG
	public static final int PNG = 1; // Take a picture of type PNG

	private Uri imageUri; // Uri of captured image
	private int encodingType; // Type of encoding to use
	private boolean saveToPhotoAlbum = true; // Should the picture be saved to the device's photo album

	public CordovaPlugin cordovaPlugin;
	private int numPics;

	private MediaScannerConnection conn; // Used to update gallery app with newly-written files
	private Uri scanMe; // Uri of image to be added to content store
	private Uri croppedUri;
	
	public Camera(CordovaPlugin cordovaPlugin){
		this.cordovaPlugin = cordovaPlugin;
	}

	// --------------------------------------------------------------------------
	// LOCAL METHODS
	// --------------------------------------------------------------------------
	private String getTempDirectoryPath() {
		File cache = null;
		// SD Card Mounted
		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
			cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordovaPlugin.cordova.getActivity().getPackageName() + "/cache/");
		}// Use internal storage
		else {
			cache = cordovaPlugin.cordova.getActivity().getCacheDir();
		}
		// Create the cache directory if it doesn't exist
		cache.mkdirs();
		return cache.getAbsolutePath();
	}

	/**
	 * Take a picture with the camera. When an image is captured or the camera
	 * view is cancelled, the result is returned in
	 * CordovaActivity.onActivityResult, which forwards the result to
	 * this.onActivityResult.
	 * 
	 * The image can either be returned as a base64 string or a URI that points
	 * to the file. To display base64 string in an img tag, set the source to:
	 * img.src="data:image/jpeg;base64,"+result; or to display URI in an img tag
	 * img.src=result;
	 * 
	 * @param quality
	 *            Compression quality hint (0-100: 0=low quality & high
	 *            compression, 100=compress of max quality)
	 * @param returnType
	 *            Set the type of image to return.
	 */
	public void takePicture(int returnType, int encodingType) {
		// Save the number of images currently on disk for later
		this.numPics = queryImgDB(whichContentStore()).getCount();
		// Display camera
		Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
		// Specify file so that large image is captured and returned
		File photo = createCaptureFile(encodingType);
		intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
		this.imageUri = Uri.fromFile(photo);

		if (cordovaPlugin != null) {
			cordovaPlugin.cordova.startActivityForResult(cordovaPlugin, intent, returnType);
		}
	}
	
	/**
	 * Get image from photo library.
	 * 
	 * @param quality
	 *            Compression quality hint (0-100: 0=low quality & high
	 *            compression, 100=compress of max quality)
	 * @param srcType
	 *            The album to get image from.
	 * @param returnType
	 *            Set the type of image to return.
	 * @param encodingType
	 */
	public void getImage(int returnType, int encodingType) {
		Intent intent = new Intent();
		String title = "选择图片";
		croppedUri = null;
		intent.setType("image/*");
		intent.setAction(Intent.ACTION_GET_CONTENT);
		intent.addCategory(Intent.CATEGORY_OPENABLE);
		if (cordovaPlugin != null) {
			cordovaPlugin.cordova.startActivityForResult(cordovaPlugin, Intent.createChooser(intent, new String(title)), returnType);
		}
	}
	
	/**
	 * Applies all needed transformation to the image received from the gallery.
	 * 
	 * @param destType
	 *            In which form should we return the image
	 * @param intent
	 *            An Intent, which can return result data to the caller (various
	 *            data can be attached to Intent "extras").
	 */
	public String processResultFromGallery(Intent intent) {
		Uri uri = intent.getData();
		if (uri == null) {
			if (croppedUri != null) {
				uri = croppedUri;
			} else {
				return null;
			}
		}
		try{
			String imgPath = uri.toString();
			if(imgPath.indexOf("com.android.providers.media.documents") != -1){
				imgPath = URLDecoder.decode(imgPath, "UTF-8");
				imgPath = imgPath.replace("content://", "");
				String id = imgPath.split(":")[1];
				imgPath = AlbumUtil.getFilePathFromID(new String[] { id }, cordovaPlugin.cordova.getActivity());
				imgPath = "file://" + imgPath;
			}else if(imgPath.indexOf("content://") != -1){
				imgPath = AlbumUtil.getFilePathFromContentUri(Uri.parse(imgPath), cordovaPlugin.cordova.getActivity());
				imgPath = "file://" + imgPath;
			}
			
			return imgPath;
		}catch(Exception e){
			return null;
		}
	}

	/**
	 * Create a file in the applications temporary directory based upon the
	 * supplied encoding.
	 * 
	 * @param encodingType
	 *            of the image to be taken
	 * @return a File object pointing to the temporary picture
	 */
	private File createCaptureFile(int encodingType) {
		File photo = null;
		if (encodingType == JPEG) {
			photo = new File(getTempDirectoryPath(), ".Pic.jpg");
		} else if (encodingType == PNG) {
			photo = new File(getTempDirectoryPath(), ".Pic.png");
		} else {
			throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType);
		}
		return photo;
	}

	/**
	 * Applies all needed transformation to the image received from the camera.
	 * 
	 * @param intent
	 *            An Intent, which can return result data to the caller (various
	 *            data can be attached to Intent "extras").
	 */
	public String processResultFromCamera(Intent intent) throws Exception {
		ExifHelper exif = new ExifHelper();
		try {
			if (this.encodingType == JPEG) {
				exif.createInFile(getTempDirectoryPath() + "/.Pic.jpg");
				exif.readExifData();
			} else if (this.encodingType == PNG) {
				exif.createInFile(getTempDirectoryPath() + "/.Pic.png");
				exif.readExifData();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		Bitmap bitmap = null;
		try{
			Uri uri = null;
			Uri inputUri = getUriFromMediaStore();
			try {
				uri = Uri.fromFile(new File(FileHelper.getRealPath(inputUri, cordovaPlugin.cordova)));
			} catch (Exception e) {
				uri = null;
			}
	
			if (uri == null)
				return null;
			
			writeUncompressedImage(uri);
			this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
			
			return uri.toString();
		}finally{
			bitmap = null;
		}
	}

	/**
	 * In the special case where the default width, height and quality are
	 * unchanged we just write the file out to disk saving the expensive
	 * Bitmap.compress function.
	 * 
	 * @param uri
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	private void writeUncompressedImage(Uri uri) throws Exception {
		FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
		OutputStream os = cordovaPlugin.cordova.getActivity().getContentResolver().openOutputStream(uri);
		byte[] buffer = new byte[4096];
		int len;
		while ((len = fis.read(buffer)) != -1) {
			os.write(buffer, 0, len);
		}
		os.flush();
		os.close();
		fis.close();
	}

	/**
	 * Create entry in media store for image
	 * 
	 * @return uri
	 */
	private Uri getUriFromMediaStore() {
		ContentValues values = new ContentValues();
		values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
		Uri uri;
		try {
			uri = cordovaPlugin.cordova
					.getActivity()
					.getContentResolver()
					.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
		} catch (RuntimeException e) {
			try {
				uri = cordovaPlugin.cordova.getActivity().getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI,values);
			} catch (RuntimeException ex) {
				return null;
			}
		}
		return uri;
	}



	/**
	 * Creates a cursor that can be used to determine how many images we have.
	 * 
	 * @return a cursor
	 */
	private Cursor queryImgDB(Uri contentStore) {
		return cordovaPlugin.cordova.getActivity().getContentResolver().query(contentStore,new String[] { MediaStore.Images.Media._ID }, null,null, null);
	}

	/**
	 * Cleans up after picture taking. Checking for duplicates and that kind of
	 * stuff.
	 * 
	 * @param newImage
	 */
	private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
		if (bitmap != null) {
			bitmap.recycle();
		}
		// Clean up initial camera-written image file.
		(new File(FileHelper.stripFileProtocol(oldImage.toString()))).delete();

		checkForDuplicateImage(imageType);
		// Scan for the gallery to update pic refs in gallery
		if (this.saveToPhotoAlbum && newImage != null) {
			this.scanForGallery(newImage);
		}

		System.gc();
	}

	/**
	 * Used to find out if we are in a situation where the Camera Intent adds to
	 * images to the content store. If we are using a FILE_URI and the number of
	 * images in the DB increases by 2 we have a duplicate, when using a
	 * DATA_URL the number is 1.
	 * 
	 * @param type
	 *            FILE_URI or DATA_URL
	 */
	private void checkForDuplicateImage(int type) {
		int diff = 1;
		Uri contentStore = whichContentStore();
		Cursor cursor = queryImgDB(contentStore);
		int currentNumOfImages = cursor.getCount();

		if (type == FILE_URI && this.saveToPhotoAlbum) {
			diff = 2;
		}

		// delete the duplicate file if the difference is 2 for file URI or 1
		// for Data URL
		if ((currentNumOfImages - numPics) == diff) {
			cursor.moveToLast();
			int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
			if (diff == 2) {
				id--;
			}
			Uri uri = Uri.parse(contentStore + "/" + id);
			cordovaPlugin.cordova.getActivity().getContentResolver().delete(uri, null, null);
			cursor.close();
		}
	}

	/**
	 * Determine if we are storing the images in internal or external storage
	 * 
	 * @return Uri
	 */
	private Uri whichContentStore() {
		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
			return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
		} else {
			return MediaStore.Images.Media.INTERNAL_CONTENT_URI;
		}
	}


	private void scanForGallery(Uri newImage) {
		this.scanMe = newImage;
		if (this.conn != null) {
			this.conn.disconnect();
		}
		this.conn = new MediaScannerConnection(cordovaPlugin.cordova.getActivity().getApplicationContext(), this);
		conn.connect();
	}

	public void onMediaScannerConnected() {
		try {
			this.conn.scanFile(this.scanMe.toString(), "image/*");
		} catch (Exception e) {
		}
	}

	public void onScanCompleted(String path, Uri uri) {
		this.conn.disconnect();
	}
}